Micron Document
C.S.Burner 🪙 ━►🔥GIT Node

Commit 19079f5dd4e86cab6436cb46d86c0dcce8d66ecb


Parents : 125b63c
Author : Ivan <e318cbc04468bd574db2b4523dddd710>
Signature : T66BB85Valid, signed by author
Date : 2026-09-12T09:01:04-05:00

refactor(messages): extract paper ingest state into usePaperIngest

Move the paper-message ingest cluster (pending hash, identity-scoped
ingested map, WS ingest request, ingest result handler) into
js/messages/usePaperIngest.js bound via setup() merge; identity key and
$t arrive via options.

Changes
Diff

diff --git a/meshchatx/src/frontend/components/messages/ConversationViewer.vue b/meshchatx/src/frontend/components/messages/ConversationViewer.vue
index 1e1bc8a6..9f326034 100644
--- a/meshchatx/src/frontend/components/messages/ConversationViewer.vue
+++ b/meshchatx/src/frontend/components/messages/ConversationViewer.vue
@@ -1630,13 +1630,6 @@ import {
collectImageFilesFromDataTransfer as collectImagesFromDataTransfer,
extractClipboardImageFiles,
} from "./conversationMessageHelpers.js";
-import {
- isPaperMessageIngested as isPaperMessageIngestedStored,
- listIngestedPaperMessageHashes,
- markPaperMessageIngested,
- normalizePaperIngestMessageHash,
- shouldMarkPaperIngestFromResultStatus,
-} from "./conversationPaperIngest.js";
import ConversationPeerHeader from "./ConversationPeerHeader.vue";
import ConversationMessageEntry from "./ConversationMessageEntry.vue";
import ConversationMessageListVirtual from "./ConversationMessageListVirtual.vue";
@@ -1701,6 +1694,7 @@ import { createOutboundQueue } from "../../js/outboundSendQueue";
import { useImageModal } from "../../js/messages/useImageModal.js";
import { useAudioAttachment } from "../../js/messages/useAudioAttachment.js";
import { useMessageDrafts } from "../../js/messages/useMessageDrafts.js";
+import { usePaperIngest } from "../../js/messages/usePaperIngest.js";
import {
isOpportunisticDeferredDelivery as isOpportunisticDeferredDeliveryStatus,
lxmfStateWouldRegress,
@@ -1794,6 +1788,10 @@ export default {
}
},
}),
+ ...usePaperIngest({
+ getIdentityKey: () => inst?.proxy._draftIdentityKey(),
+ t: (key) => inst?.proxy.$t(key),
+ }),
};
},
data() {
@@ -1859,8 +1857,6 @@ export default {
},
isPaperMessageModalOpen: false,
paperMessageHash: null,
- pendingPaperIngestMessageHash: null,
- ingestedPaperMessageHashes: {},
isRawMessageModalOpen: false,
rawMessageData: null,
hasTranslator: false,
@@ -3099,24 +3095,6 @@ export default {
this.initialLoad();
}
},
- reloadIngestedPaperMessageHashes() {
- const hashes = listIngestedPaperMessageHashes(this._draftIdentityKey());
- const next = {};
- for (const hash of hashes) {
- next[hash] = true;
- }
- this.ingestedPaperMessageHashes = next;
- },
- isPaperMessageIngested(chatItem) {
- const hash = normalizePaperIngestMessageHash(chatItem?.lxmf_message?.hash);
- if (!hash) {
- return false;
- }
- if (this.ingestedPaperMessageHashes[hash]) {
- return true;
- }
- return isPaperMessageIngestedStored(this._draftIdentityKey(), hash);
- },
close() {
this.$emit("close");
},
@@ -3441,37 +3419,6 @@ export default {
ToastUtils.error(this.$t("messages.failed_add_contact"));
}
},
- async ingestPaperMessage(uri, messageHash = null) {
- try {
- const hash = normalizePaperIngestMessageHash(messageHash);
- this.pendingPaperIngestMessageHash = hash || null;
- WebSocketConnection.send(
- JSON.stringify({
- type: "lxm.ingest_uri",
- uri: uri,
- })
- );
- ToastUtils.info(this.$t("messages.ingesting_paper_message"));
- } catch (e) {
- console.error(e);
- this.pendingPaperIngestMessageHash = null;
- ToastUtils.error(this.$t("messages.failed_ingest_paper"));
- }
- },
- onLxmIngestUriResultEvent(json) {
- const pendingHash = this.pendingPaperIngestMessageHash;
- this.pendingPaperIngestMessageHash = null;
- if (!pendingHash || !shouldMarkPaperIngestFromResultStatus(json?.status)) {
- return;
- }
- const list = markPaperMessageIngested(this._draftIdentityKey(), pendingHash);
- const next = { ...this.ingestedPaperMessageHashes };
- for (const hash of list) {
- next[hash] = true;
- }
- next[pendingHash] = true;
- this.ingestedPaperMessageHashes = next;
- },
async generatePaperMessageFromComposition() {
if (!this.canSendMessage) return;

diff --git a/meshchatx/src/frontend/js/messages/usePaperIngest.js b/meshchatx/src/frontend/js/messages/usePaperIngest.js
new file mode 100644
index 00000000..6fd1eb65
--- /dev/null
+++ b/meshchatx/src/frontend/js/messages/usePaperIngest.js
@@ -0,0 +1,93 @@
+// @ts-check
+
+import { ref } from "vue";
+
+import ToastUtils from "../ToastUtils.js";
+import WebSocketConnection from "../WebSocketConnection.js";
+import {
+ isPaperMessageIngested as isPaperMessageIngestedStored,
+ listIngestedPaperMessageHashes,
+ markPaperMessageIngested,
+ normalizePaperIngestMessageHash,
+ shouldMarkPaperIngestFromResultStatus,
+} from "../../components/messages/conversationPaperIngest.js";
+
+/**
+ * Paper-message ingest state for ConversationViewer: the pending ingest
+ * result hash, the identity-scoped ingested-hash map, WS ingest request,
+ * and the lxm.ingest_uri result handler.
+ *
+ * options.getIdentityKey resolves the active identity bucket the ingested
+ * hashes are stored under. options.t is the host i18n function.
+ */
+export function usePaperIngest(options = {}) {
+ const getIdentityKey = options.getIdentityKey || (() => "_");
+ const t = options.t || ((key) => key);
+
+ const pendingPaperIngestMessageHash = ref(null);
+ /** @type {import("vue").Ref<Record<string, boolean>>} */
+ const ingestedPaperMessageHashes = ref({});
+
+ function reloadIngestedPaperMessageHashes() {
+ const hashes = listIngestedPaperMessageHashes(getIdentityKey());
+ /** @type {Record<string, boolean>} */
+ const next = {};
+ for (const hash of hashes) {
+ next[hash] = true;
+ }
+ ingestedPaperMessageHashes.value = next;
+ }
+
+ function isPaperMessageIngested(chatItem) {
+ const hash = normalizePaperIngestMessageHash(chatItem?.lxmf_message?.hash);
+ if (!hash) {
+ return false;
+ }
+ if (ingestedPaperMessageHashes.value[hash]) {
+ return true;
+ }
+ return isPaperMessageIngestedStored(getIdentityKey(), hash);
+ }
+
+ async function ingestPaperMessage(uri, messageHash = null) {
+ try {
+ const hash = normalizePaperIngestMessageHash(messageHash);
+ pendingPaperIngestMessageHash.value = hash || null;
+ WebSocketConnection.send(
+ JSON.stringify({
+ type: "lxm.ingest_uri",
+ uri: uri,
+ })
+ );
+ ToastUtils.info(t("messages.ingesting_paper_message"));
+ } catch (e) {
+ console.error(e);
+ pendingPaperIngestMessageHash.value = null;
+ ToastUtils.error(t("messages.failed_ingest_paper"));
+ }
+ }
+
+ function onLxmIngestUriResultEvent(json) {
+ const pendingHash = pendingPaperIngestMessageHash.value;
+ pendingPaperIngestMessageHash.value = null;
+ if (!pendingHash || !shouldMarkPaperIngestFromResultStatus(json?.status)) {
+ return;
+ }
+ const list = markPaperMessageIngested(getIdentityKey(), pendingHash);
+ const next = { ...ingestedPaperMessageHashes.value };
+ for (const hash of list) {
+ next[hash] = true;
+ }
+ next[pendingHash] = true;
+ ingestedPaperMessageHashes.value = next;
+ }
+
+ return {
+ pendingPaperIngestMessageHash,
+ ingestedPaperMessageHashes,
+ reloadIngestedPaperMessageHashes,
+ isPaperMessageIngested,
+ ingestPaperMessage,
+ onLxmIngestUriResultEvent,
+ };
+}

diff --git a/tests/frontend/usePaperIngest.test.js b/tests/frontend/usePaperIngest.test.js
new file mode 100644
index 00000000..2d1c9186
--- /dev/null
+++ b/tests/frontend/usePaperIngest.test.js
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: 0BSD
+
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { usePaperIngest } from "../../meshchatx/src/frontend/js/messages/usePaperIngest.js";
+
+vi.mock("../../meshchatx/src/frontend/js/WebSocketConnection.js", () => ({
+ default: { send: vi.fn() },
+}));
+
+import WebSocketConnection from "../../meshchatx/src/frontend/js/WebSocketConnection.js";
+
+describe("usePaperIngest", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ localStorage.clear();
+ });
+
+ it("ingest sends lxm.ingest_uri and tracks the pending hash", async () => {
+ const p = usePaperIngest({ getIdentityKey: () => "idA" });
+ await p.ingestPaperMessage("lxm://hash:payload", "aa".repeat(16));
+ expect(WebSocketConnection.send).toHaveBeenCalled();
+ const sent = JSON.parse(WebSocketConnection.send.mock.calls[0][0]);
+ expect(sent.type).toBe("lxm.ingest_uri");
+ expect(p.pendingPaperIngestMessageHash.value).toBe("aa".repeat(16));
+ });
+
+ it("successful ingest result marks the hash ingested", () => {
+ const p = usePaperIngest({ getIdentityKey: () => "idA" });
+ const hash = "bb".repeat(16);
+ p.pendingPaperIngestMessageHash.value = hash;
+ p.onLxmIngestUriResultEvent({ status: "success" });
+ expect(p.pendingPaperIngestMessageHash.value).toBeNull();
+ expect(p.isPaperMessageIngested({ lxmf_message: { hash } })).toBe(true);
+ });
+
+ it("warning/error ingest results do not mark the hash", () => {
+ const p = usePaperIngest({ getIdentityKey: () => "idA" });
+ const hash = "cc".repeat(16);
+ p.pendingPaperIngestMessageHash.value = hash;
+ p.onLxmIngestUriResultEvent({ status: "error" });
+ expect(p.isPaperMessageIngested({ lxmf_message: { hash } })).toBe(false);
+ });
+
+ it("isPaperMessageIngested returns false for items without a hash", () => {
+ const p = usePaperIngest();
+ expect(p.isPaperMessageIngested({})).toBe(false);
+ expect(p.isPaperMessageIngested(null)).toBe(false);
+ });
+
+ it("reload populates the ingested map from storage", () => {
+ const p = usePaperIngest({ getIdentityKey: () => "idA" });
+ const hash = "dd".repeat(16);
+ p.pendingPaperIngestMessageHash.value = hash;
+ p.onLxmIngestUriResultEvent({ status: "success" });
+ // fresh composable sees the persisted hash after reload
+ const p2 = usePaperIngest({ getIdentityKey: () => "idA" });
+ p2.reloadIngestedPaperMessageHashes();
+ expect(p2.isPaperMessageIngested({ lxmf_message: { hash } })).toBe(true);
+ });
+});

Served by rngit 1.5.4 - Generated in 0.03s